package alipay

import (
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/pkg/util"
	"github.com/go-pay/gopay/pkg/xhttp"
	"github.com/go-pay/gopay/pkg/xlog"
	"hash"
	"log"
	"net/http"
	"net/url"
	"reflect"
	"sort"
	"strings"
	"sync"
	"time"
)

const (
	// URL
	baseUrl            = "https://openapi.alipay.com/gateway.do"
	sandboxBaseUrl     = "https://openapi.alipaydev.com/gateway.do"
	baseUrlUtf8        = "https://openapi.alipay.com/gateway.do?charset=utf-8"
	sandboxBaseUrlUtf8 = "https://openapi.alipaydev.com/gateway.do?charset=utf-8"

	LocationShanghai          = "Asia/Shanghai"
	PKCS1            PKCSType = 1 // 非Java
	PKCS8            PKCSType = 2 // Java
	RSA                       = "RSA"
	RSA2                      = "RSA2"
)

type BodyMap map[string]interface{}

var mu = new(sync.RWMutex)

// 设置参数
func (bm BodyMap) Set(key string, value interface{}) BodyMap {
	mu.Lock()
	bm[key] = value
	mu.Unlock()
	return bm
}

// 获取string参数
func (bm BodyMap) Get(key string) string {
	val, ok := bm[key]
	if !ok {
		return ""
	}
	_, oks := val.(string)
	if oks {
		return val.(string)
	} else {
		b, err := json.Marshal(val)
		if err != nil {
			return ""
		}
		str := string(b)
		if str == "null" {
			return ""
		}
		return str
	}
}

// 删除参数
func (bm BodyMap) Remove(key string) {
	mu.Lock()
	delete(bm, key)
	mu.Unlock()
}

type PKCSType uint8
type Client struct {
	AppId              string
	PrivateKeyType     PKCSType
	PrivateKey         string
	LocationName       string
	AppCertSN          string
	AliPayPublicCertSN string
	AliPayRootCertSN   string
	Charset            string
	SignType           string
	AppAuthToken       string
	IsProd             bool
	aliPayPkContent    []byte // 支付宝证书公钥内容 alipayCertPublicKey_RSA2.crt
	autoSign           bool
	DebugSwitch        gopay.DebugSwitch
	location           *time.Location
	mu                 sync.RWMutex
}

func NewClient(appId, privateKey string, isProd bool) (client *Client) {
	return &Client{
		AppId:      appId,
		PrivateKey: privateKey,
		IsProd:     isProd,
	}
}

// 获取参数签名
func (a *Client) GetAliSing() (info, sing string, err error) {
	bm := make(BodyMap)
	bm.Set("app_id", a.AppId)
	bm.Set("pid", "2088041105272721")
	bm.Set("apiname", "com.alipay.account.auth")
	bm.Set("methodname", "alipay.open.auth.sdk.code.get")
	bm.Set("app_name", "mc")
	bm.Set("biz_type", "openservice")
	bm.Set("product_id", "APP_FAST_LOGIN")
	bm.Set("scope", "kuaijie")
	bm.Set("target_id", bm.GetString("pid"))
	bm.Set("auth_type", "AUTHACCOUNT")
	bm.Set("sign_type", a.SignType)

	if sing, err = GetRsaSign(bm, a.SignType, a.PrivateKeyType, a.PrivateKey); err != nil {
		return
	}
	info = bm.EncodeAliPaySignParams()
	return info, sing, err
}

//向支付宝发送请求
func (a *Client) DoAliPay(bm BodyMap, method string, authToken ...string) (bs []byte, err error) {
	var (
		bodyStr, url string
		bodyBs       []byte
		aat          string
	)
	pubBody := make(BodyMap)

	//if bm["auth_token"] != nil {
	//	pubBody.Set("auth_token", bm["auth_token"])
	//	bm.Remove("auth_token")
	//}
	if bm["code"] != nil {
		pubBody.Set("code", bm["code"])
		pubBody.Set("grant_type", bm["grant_type"])
		bm = nil
	}

	if bm != nil {
		aat = bm.GetString("app_auth_token")
		bm.Remove("app_auth_token")

		if bodyBs, err = json.Marshal(bm); err != nil {
			return nil, fmt.Errorf("json.Marshal：%w", err)
		}
		bodyStr = string(bodyBs)
	}

	pubBody.Set("app_id", a.AppId)
	pubBody.Set("method", method)
	pubBody.Set("format", "JSON")
	if a.AppCertSN != util.NULL {
		pubBody.Set("app_cert_sn", a.AppCertSN)
	}
	if a.AliPayRootCertSN != util.NULL {
		pubBody.Set("alipay_root_cert_sn", a.AliPayRootCertSN)
	}
	pubBody.Set("charset", "utf-8")
	if a.Charset != util.NULL {
		pubBody.Set("charset", a.Charset)
	}
	pubBody.Set("sign_type", RSA2)
	if a.SignType != util.NULL {
		pubBody.Set("sign_type", a.SignType)
	}
	pubBody.Set("timestamp", time.Now().Format(util.TimeLayout))
	if a.LocationName != util.NULL && a.location != nil {
		pubBody.Set("timestamp", time.Now().In(a.location).Format(util.TimeLayout))
	}
	pubBody.Set("version", "1.0")
	if aat == util.NULL && a.AppAuthToken != util.NULL {
		pubBody.Set("app_auth_token", a.AppAuthToken)
	}
	if method == "alipay.user.info.share" {
		pubBody.Set("auth_token", authToken[0])
	}

	if bodyStr != util.NULL {
		pubBody.Set("biz_content", bodyStr)
	}
	sign, err := GetRsaSign(pubBody, pubBody.GetString("sign_type"), a.PrivateKeyType, a.PrivateKey)
	if err != nil {
		return nil, fmt.Errorf("GetRsaSign Error: %v", err)
	}
	pubBody.Set("sign", sign)
	if a.DebugSwitch == gopay.DebugOn {
		req, _ := json.Marshal(pubBody)
		xlog.Debugf("Alipay_Request: %s", req)
	}
	param := FormatURLParam(pubBody)
	switch method {
	case "alipay.trade.app.pay", "alipay.fund.auth.order.app.freeze":
		return []byte(param), nil
	case "alipay.trade.wap.pay", "alipay.trade.page.pay", "alipay.user.certify.open.certify":
		if !a.IsProd {
			return []byte(sandboxBaseUrl + "?" + param), nil
		}
		return []byte(baseUrl + "?" + param), nil
	default:
		httpClient := xhttp.NewClient()
		if a.IsProd {
			url = baseUrlUtf8
		} else {
			url = sandboxBaseUrlUtf8
		}
		res, bs, errs := httpClient.Type(xhttp.TypeForm).Post(url).SendString(param).EndBytes(context.Background())
		if errs != nil {
			return nil, errs
		}
		if a.DebugSwitch == gopay.DebugOn {
			xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs))
		}
		if res.StatusCode != 200 {
			return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode)
		}
		return bs, nil
	}
}

// 获取支付宝参数签名
//	bm：签名参数
//	signType：签名类型，alipay.RSA 或 alipay.RSA2
//	t：私钥类型，alipay.PKCS1 或 alipay.PKCS1，默认 PKCS1
//	privateKey：应用私钥，支持PKCS1和PKCS8
func GetRsaSign(bm BodyMap, signType string, t PKCSType, privateKey string) (sign string, err error) {
	var (
		block          *pem.Block
		h              hash.Hash
		key            *rsa.PrivateKey
		hashs          crypto.Hash
		encryptedBytes []byte
	)
	pk := FormatPrivateKey(privateKey)

	if block, _ = pem.Decode([]byte(pk)); block == nil {
		return util.NULL, errors.New("pem.Decode：privateKey decode error")
	}

	switch t {
	case PKCS1:
		if key, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
			return util.NULL, err
		}
	case PKCS8:
		pkcs8Key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
		if err != nil {
			return util.NULL, err
		}
		pk8, ok := pkcs8Key.(*rsa.PrivateKey)
		if !ok {
			return util.NULL, errors.New("parse PKCS8 key error")
		}
		key = pk8
	default:
		if key, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
			return util.NULL, err
		}
	}

	switch signType {
	case RSA:
		h = sha1.New()
		hashs = crypto.SHA1
	case RSA2:
		h = sha256.New()
		hashs = crypto.SHA256
	default:
		h = sha256.New()
		hashs = crypto.SHA256
	}
	if _, err = h.Write([]byte(bm.EncodeAliPaySignParams())); err != nil {
		return
	}
	if encryptedBytes, err = rsa.SignPKCS1v15(rand.Reader, key, hashs, h.Sum(nil)); err != nil {
		return
	}
	sign = base64.StdEncoding.EncodeToString(encryptedBytes)
	return
}

// =============================== 获取SignData ===============================

// 需注意的是，公钥签名模式和公钥证书签名模式的不同之处
//	验签文档：https://opendocs.alipay.com/open/200/106120
func (a *Client) GetSignData(bs []byte, alipayCertSN string) (signData string, err error) {
	var (
		str        = string(bs)
		indexStart = strings.Index(str, `_response":`)
		indexEnd   int
	)
	if alipayCertSN != "" {
		// 公钥证书模式
		indexEnd = strings.Index(str, `,"alipay_cert_sn":`)
		signData = str[indexStart+11 : indexEnd]
		if alipayCertSN != a.AliPayPublicCertSN {
			return signData, errors.New("当前使用的支付宝公钥证书SN与网关响应报文中的SN不匹配")
		}
		return
	}
	// 普通公钥模式
	indexEnd = strings.Index(str, `,"sign":`)
	signData = str[indexStart+11 : indexEnd]
	return
}

// =============================== 同步验签 ===============================

func (a *Client) AutoVerifySignByCert(sign, signData string, signDataErr error) (err error) {
	if a.autoSign && a.aliPayPkContent != nil {
		if a.DebugSwitch == gopay.DebugOn {
			xlog.Debugf("Alipay_SyncSignData: %s, Sign=[%s]", signData, sign)
		}
		// 只有证书验签时，才可能出现此error
		if signDataErr != nil {
			return signDataErr
		}
		var (
			block     *pem.Block
			pubKey    *x509.Certificate
			publicKey *rsa.PublicKey
			ok        bool
		)
		signBytes, _ := base64.StdEncoding.DecodeString(sign)
		if block, _ = pem.Decode(a.aliPayPkContent); block == nil {
			return errors.New("支付宝公钥Decode错误")
		}
		if pubKey, err = x509.ParseCertificate(block.Bytes); err != nil {
			return fmt.Errorf("x509.ParseCertificate：%w", err)
		}
		if publicKey, ok = pubKey.PublicKey.(*rsa.PublicKey); !ok {
			return errors.New("支付宝公钥转换错误")
		}
		hashs := crypto.SHA256
		h := hashs.New()
		h.Write([]byte(signData))
		return rsa.VerifyPKCS1v15(publicKey, hashs, h.Sum(nil), signBytes)
	}
	return nil
}

// 格式化请求URL参数
func FormatURLParam(body BodyMap) (urlParam string) {
	v := url.Values{}
	for key, value := range body {
		v.Add(key, value.(string))
	}
	return v.Encode()
}

// FormatPrivateKey 格式化 普通应用秘钥
func FormatPrivateKey(privateKey string) (pKey string) {
	var buffer strings.Builder
	buffer.WriteString("-----BEGIN RSA PRIVATE KEY-----\n")
	rawLen := 64
	keyLen := len(privateKey)
	raws := keyLen / rawLen
	temp := keyLen % rawLen
	if temp > 0 {
		raws++
	}
	start := 0
	end := start + rawLen
	for i := 0; i < raws; i++ {
		if i == raws-1 {
			buffer.WriteString(privateKey[start:])
		} else {
			buffer.WriteString(privateKey[start:end])
		}
		buffer.WriteByte('\n')
		start += rawLen
		end = start + rawLen
	}
	buffer.WriteString("-----END RSA PRIVATE KEY-----\n")
	pKey = buffer.String()
	return
}

// 获取参数转换string
func (bm BodyMap) GetString(key string) string {
	if bm == nil {
		return ""
	}
	mu.RLock()
	defer mu.RUnlock()
	value, ok := bm[key]
	if !ok {
		return ""
	}
	v, ok := value.(string)
	if !ok {
		return convertToString(value)
	}
	return v
}

func convertToString(v interface{}) (str string) {
	if v == nil {
		return ""
	}
	var (
		bs  []byte
		err error
	)
	if bs, err = json.Marshal(v); err != nil {
		return ""
	}
	str = string(bs)
	return
}

func (bm BodyMap) EncodeAliPaySignParams() string {
	var (
		buf     strings.Builder
		keyList []string
	)
	mu.RLock()
	for k := range bm {
		keyList = append(keyList, k)
	}
	sort.Strings(keyList)
	mu.RUnlock()
	for _, k := range keyList {
		if v := bm.GetString(k); v != "" {
			buf.WriteString(k)
			buf.WriteByte('=')
			buf.WriteString(v)
			buf.WriteByte('&')
		}
	}
	if buf.Len() <= 0 {
		return ""
	}
	return buf.String()[:buf.Len()-1]
}

func ParseNotifyToBodyMap(req *http.Request) (bm BodyMap, err error) {
	if err = req.ParseForm(); err != nil {
		return nil, err
	}
	var form map[string][]string = req.Form
	bm = make(BodyMap, len(form)+1)
	for k, v := range form {
		if len(v) == 1 {
			bm.Set(k, v[0])
		}
	}
	return
}

func VerifySign(aliPayPublicKey string, notifyBean interface{}) (ok bool, err error) {

	//log.Println("验签Map", notifyBean)

	if aliPayPublicKey == util.NULL || notifyBean == nil {
		return false, errors.New("aliPayPublicKey or notifyBean is nil")
	}
	var (
		bodySign     string
		bodySignType string
		signData     string
		bm           = make(BodyMap)
	)
	if reflect.ValueOf(notifyBean).Kind() == reflect.Map {
		if bm, ok = notifyBean.(BodyMap); ok {
			bodySign = bm.GetString("sign")
			bodySignType = bm.GetString("sign_type")
			bm.Remove("sign")
			bm.Remove("sign_type")
			signData = bm.EncodeAliPaySignParams()
		}
	} else {
		bs, err := json.Marshal(notifyBean)
		if err != nil {
			return false, fmt.Errorf("json.Marshal：%w", err)
		}
		if err = json.Unmarshal(bs, &bm); err != nil {
			return false, fmt.Errorf("json.Unmarshal(%s)：%w", string(bs), err)
		}
		bodySign = bm.GetString("sign")
		bodySignType = bm.GetString("sign_type")
		bm.Remove("sign")
		bm.Remove("sign_type")
		signData = bm.EncodeAliPaySignParams()
	}

	pKey := FormatPublicKey(aliPayPublicKey)
	log.Println("验签参数", signData, bodySign, bodySignType, pKey)

	if err = verifySign(signData, bodySign, bodySignType, pKey); err != nil {
		return false, err
	}
	return true, nil
}

// FormatPublicKey 格式化 普通支付宝公钥
func FormatPublicKey(publicKey string) (pKey string) {
	var buffer strings.Builder
	buffer.WriteString("-----BEGIN PUBLIC KEY-----\n")
	rawLen := 64
	keyLen := len(publicKey)
	raws := keyLen / rawLen
	temp := keyLen % rawLen
	if temp > 0 {
		raws++
	}
	start := 0
	end := start + rawLen
	for i := 0; i < raws; i++ {
		if i == raws-1 {
			buffer.WriteString(publicKey[start:])
		} else {
			buffer.WriteString(publicKey[start:end])
		}
		buffer.WriteByte('\n')
		start += rawLen
		end = start + rawLen
	}
	buffer.WriteString("-----END PUBLIC KEY-----\n")
	pKey = buffer.String()
	return
}

func verifySign(signData, sign, signType, aliPayPublicKey string) (err error) {
	var (
		h         hash.Hash
		hashs     crypto.Hash
		block     *pem.Block
		pubKey    interface{}
		publicKey *rsa.PublicKey
		ok        bool
	)
	signBytes, _ := base64.StdEncoding.DecodeString(sign)
	if block, _ = pem.Decode([]byte(aliPayPublicKey)); block == nil {
		return errors.New("支付宝公钥Decode错误")
	}
	if pubKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
		return fmt.Errorf("x509.ParsePKIXPublicKey：%w", err)
	}
	if publicKey, ok = pubKey.(*rsa.PublicKey); !ok {
		return errors.New("支付宝公钥转换错误")
	}
	switch signType {
	case RSA:
		hashs = crypto.SHA1
	case RSA2:
		hashs = crypto.SHA256
	default:
		hashs = crypto.SHA256
	}
	h = hashs.New()
	h.Write([]byte(signData))
	return rsa.VerifyPKCS1v15(publicKey, hashs, h.Sum(nil), signBytes)
}
